package com.gonsin.gmessage.logic.proxy;

import com.gonsin.gmessage.annotations.*;
import com.gonsin.gmessage.annotations.enums.ZoomDescribe;
import com.gonsin.gmessage.logic.GMessage;
import com.gonsin.gmessage.logic.exceptions.GMessageException;
import com.gonsin.gmessage.logic.model.GMessageModel;
import com.gonsin.gmessage.logic.render.args.ArgsParamDescription;
import com.gonsin.gmessage.logic.render.MessageDescription;
import com.gonsin.gmessage.logic.render.RenderManager;
import com.gonsin.gmessage.logic.render.SenderDescription;
import com.gonsin.gmessage.logic.utils.ClassUtil;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.checkerframework.checker.units.qual.A;
import org.checkerframework.checker.units.qual.C;
import org.eclipse.paho.mqttv5.client.IMqttToken;
import org.eclipse.paho.mqttv5.client.MqttActionListener;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.gateway.RequestReplyExchanger;
import org.springframework.util.*;

import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

@Slf4j
public class GMessageProxyFactoryBean extends AbstractEndpoint
        implements MethodInterceptor, FactoryBean<Object>, BeanClassLoaderAware {

    @Autowired
    private MessageSender messageSender;

    @Autowired
    private RenderManager renderManager;

    /**
     * 初始化时候的同步锁
     */
    private final Object initializationMonitor = new Object();

    private volatile ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();

    /**
     * 是否已初始化
     */
    private volatile boolean initialized = false;

    private EvaluationContext evaluationContext = new StandardEvaluationContext();

    // 代理类
    private volatile Object serviceProxy;

    private volatile Class<?> serviceInterface;

    // 每个方法的执行器
    private Map<Method, MethodExecutor> methodMap = new HashMap<>();

    public GMessageProxyFactoryBean(Class<?> serviceInterface){
        if(GMessageEnvironment.LOG){
            log.info("GMessageProxyFactoryBean 注册类为 {}", serviceInterface);
        }
        this.serviceInterface = serviceInterface;
    }


    @Override
    protected void doStart() {
//        log.info("doStart");
    }

    @Override
    protected void doStop() {
//        log.info("doStop");
    }

    @Override
    protected void onInit() {
        if(GMessageEnvironment.LOG){
            log.info("GMessageProxyFactoryBean 初始化 onInit");
        }
        synchronized (this.initializationMonitor) {
            if (this.initialized) {
                return;
            }

            Class<?> proxyInterface = this.determineServiceInterface();
            if(GMessageEnvironment.LOG){
                log.info("GMessageProxyFactoryBean 初始化 {}", proxyInterface);
            }
            this.serviceProxy = new ProxyFactory(proxyInterface, this)
                    .getProxy(this.beanClassLoader);

            GMessage gMessage = proxyInterface.getAnnotation(GMessage.class);
            Assert.notNull(gMessage, "应该不会为空");

            Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(proxyInterface);
            for (Method method : methods) {
                MethodExecutor messageMethod = new MethodExecutor(proxyInterface, gMessage, method);
                this.methodMap.put(method, messageMethod);
            }


            // 此处初始化用于生成MQTT文档
            createApiDoc(gMessage, methods);

            this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory());
            this.initialized = true;
        }
    }

    /**
     * 创建接口文档
     */
    private void createApiDoc(GMessage gMessage, Method[] methods) {

        // 组装接口需要的内容
        MessageDescription message = new MessageDescription();
        message.setNote(gMessage.note());
        message.setSummary(gMessage.summary());
        message.setVersion(gMessage.version());
        message.setSenders(new ArrayList<>());
        for(Method m : methods){
            SenderDescription sender = new SenderDescription();
            GSender gSender = m.getAnnotation(GSender.class);
            if(gSender == null){
                continue;
            }

            String topicUrl = generalTopicUrl(gSender, gMessage, m);

            sender.setNote(gSender.note());
            sender.setSummary(gSender.summary());
            sender.setFirstCmd(gMessage.firstCmd());
            sender.setSubCmd(gSender.subCmd());
            sender.setTopicUrl(topicUrl);
            sender.setTopicUrlDot(topicUrl.replace("/", "."));
            sender.setParameters(new ArrayList<>());
            sender.setQos(gSender.qos());
            sender.setShare(gSender.share());
            message.getSenders().add(sender);

            Parameter[] parameters = m.getParameters();
            for(Parameter p : parameters){
                GArgsIgnore ignore = p.getDeclaredAnnotation(GArgsIgnore.class);
                if(ignore != null){
                    continue;
                }
                GArgsParam gArgsParam = p.getDeclaredAnnotation(GArgsParam.class);
                if(gArgsParam != null){
                    ArgsParamDescription argsParam = createArgsParam(p, gArgsParam);
                    if(argsParam == null){
                        continue;
                    }
                    sender.getParameters().add(argsParam);
                    continue;
                }

                GArgsBody gArgsBody = p.getDeclaredAnnotation(GArgsBody.class);
                if(gArgsBody != null){
                    Type bodyClass = p.getParameterizedType();
                    if(!(bodyClass instanceof Class)){
                        throw new GMessageException("GMessage的参数不接收接口作为参数，请查看%s %s", m.getDeclaringClass().getName(), m.getName());
                    }
                    List<Field> bodyField = ClassUtil.getAllFields((Class) bodyClass);
                    if(CollectionUtils.isEmpty(bodyField)){
                        continue;
                    }
                    for(Field field : bodyField){
                        ArgsParamDescription argsParam = createArgsParamForField(0, field);
                        if(argsParam == null){
                            continue;
                        }
                        sender.getParameters().add(argsParam);
                    }
                }
            }
            sender.createJson();
            sender.createParameterList();
        }


        // 构建接口文档
        renderManager.render(gMessage.note(), message);
    }

    private String generalTopicUrl(GSender gSender, GMessage gMessage, Method m) {

        String url = "venus/" + gMessage.firstCmd() + "/" + gSender.subCmd();
        if(gSender.share()){
            url = "$SHARE/" + url;
        }
        if(gSender.zoomDescribe() == ZoomDescribe.ALL){
            return url;
        }

        String zoom = getZoomKeyName(m);
        return url + "/" + "${" + zoom + "}";

    }

    private String getZoomKeyName(Method m) {
        Parameter[] parameters = m.getParameters();
        for(Parameter p : parameters){
            GZoom z = p.getDeclaredAnnotation(GZoom.class);
            if(z != null){
                return p.getName();
            }
        }
        return null;
    }


    /**
     * 方法参数构建 ArgsParamDescription 参数描述
     * @param p
     * @param gArgsParam
     * @return
     */
    private ArgsParamDescription createArgsParam(Parameter p, GArgsParam gArgsParam){

        Type paramType = p.getParameterizedType();
        if(paramType instanceof ParameterizedType){
            ParameterizedType paramClass = (ParameterizedType) paramType;
            Type rowType = paramClass.getRawType();
            if(rowType instanceof Class){
                Class rowClass = (Class)rowType;
                // 如果是MAP类型
                if(Map.class.isAssignableFrom(rowClass)){
                    if(gArgsParam != null){
                        ArgsParamDescription argsParam = new ArgsParamDescription();
                        argsParam.setNote(gArgsParam.note());
                        argsParam.setKeyName(gArgsParam.value());
                        argsParam.setRequired(gArgsParam.required());
                        argsParam.setSummary(gArgsParam.summary());
                        argsParam.setDemo(StringUtils.isEmpty(gArgsParam.demo()) ? "{}" : gArgsParam.demo());
                        argsParam.setType("Object");
                        return argsParam;
                    }
                }

                // 如果是List 或 set 的数组
                if(Collection.class.isAssignableFrom(rowClass) || List.class.isAssignableFrom(rowClass) || Set.class.isAssignableFrom(rowClass)){
                    if(gArgsParam != null){
                        ArgsParamDescription argsParam = new ArgsParamDescription();
                        argsParam.setNote(gArgsParam.note());
                        argsParam.setKeyName(gArgsParam.value());
                        argsParam.setRequired(gArgsParam.required());
                        argsParam.setSummary(gArgsParam.summary());
                        argsParam.setDemo(gArgsParam.demo());
                        argsParam.setType("Array");


                        Type type = paramClass.getActualTypeArguments()[0];
                        if("E".equals(type.getTypeName()) || "?".equals(type.getTypeName()) || "Object".equals(type.getTypeName())){
                            return argsParam;
                        }
                        if(!(type instanceof Class)){
                            return argsParam;
                        }

                        Class actualInnerType = (Class) type;
                        // 如果列表里是基本类型
                        if(ClassUtil.isBasicClass(actualInnerType)){
                            argsParam.setBasicClassArray(true);
                            if(String.class.isAssignableFrom(actualInnerType) || Character.class.isAssignableFrom(actualInnerType)){
                                argsParam.setInnerClass("String");
                            } else {
                                argsParam.setInnerClass("Number");
                            }
//                            ArgsParamDescription inner = new ArgsParamDescription();
//                            inner.setNote(gArgsParam.note());
//                            inner.setKeyName(gArgsParam.value());
//                            inner.setRequired(gArgsParam.required());
//                            inner.setSummary(gArgsParam.summary());
//                            inner.setDemo(gArgsParam.demo());
//                            inner.setType(actualInnerType.getSimpleName());
//                            argsParam.getChild().add(inner);
                            return argsParam;
                        }


                        List<Field> bodyField = ClassUtil.getAllFields((Class) type);
                        argsParam.setChild(new ArrayList<>());
                        for(Field field : bodyField){
                            ArgsParamDescription args = createArgsParamForField(0, field);
                            if(args == null){
                                continue;
                            }
                            argsParam.getChild().add(args);
                        }
                        return argsParam;
                    }
                }
            }




        } else if(paramType instanceof Class){
            Class paramClass = (Class) paramType;

            if(ClassUtil.isBasicClass(paramClass)){
                if(gArgsParam != null){
                    ArgsParamDescription argsParam = new ArgsParamDescription();
                    argsParam.setNote(gArgsParam.note());
                    argsParam.setKeyName(gArgsParam.value());
                    argsParam.setRequired(gArgsParam.required());
                    argsParam.setSummary(gArgsParam.summary());
                    argsParam.setDemo(gArgsParam.demo());
                    argsParam.setType(paramClass.getSimpleName());
                    return argsParam;
                }
            }

            ArgsParamDescription argsParam = new ArgsParamDescription();
            argsParam.setNote(gArgsParam.note());
            argsParam.setKeyName(gArgsParam.value());
            argsParam.setRequired(gArgsParam.required());
            argsParam.setSummary(gArgsParam.summary());
            argsParam.setDemo(gArgsParam.demo());
            argsParam.setType("Object");
            argsParam.setChild(new ArrayList<>());

            List<Field> bodyField = ClassUtil.getAllFields(paramClass);
            for(Field field : bodyField){
                ArgsParamDescription args = createArgsParamForField(0, field);
                if(args == null){
                    continue;
                }
                argsParam.getChild().add(args);
            }
            return argsParam;
        }

        return null;

    }


//
//    /**
//     * 根据成员变量构建 ArgsParamDescription 参数描述
//     */
//    private ArgsParamDescription createArgsParamForType(Class paramClass){
//
//        if(ClassUtil.isBasicClass(paramClass)){
//            if(String.class.isAssignableFrom(paramClass)){
//                ArgsParamDescription args = new ArgsParamDescription();
//                args.
//            }
//            if(gArgsParam != null){
//                ArgsParamDescription argsParam = new ArgsParamDescription();
//                argsParam.setNote(gArgsParam.note());
//                argsParam.setKeyName(gArgsParam.value());
//                argsParam.setRequired(gArgsParam.required());
//                argsParam.setSummary(gArgsParam.summary());
//                argsParam.setDemo(gArgsParam.demo());
//                return argsParam;
//            }
//        }
//
//        // 列表类型
//        if(List.class.isAssignableFrom(paramClass)){
//            TypeVariable subType = paramClass.asSubclass(List.class).getTypeParameters()[0];
//            if("E".equals(subType.getName())){
//
//            }
//            createListArgsParam(gArgsParam);
//        }
//
//
//        ArgsParamDescription argsParam = new ArgsParamDescription();
//        argsParam.setNote(gArgsParam.note());
//        argsParam.setKeyName(gArgsParam.value());
//        argsParam.setRequired(gArgsParam.required());
//        argsParam.setSummary(gArgsParam.summary());
//        argsParam.setDemo(gArgsParam.demo());
//        argsParam.setChild(new ArrayList<>());
//
//        List<Field> bodyField = ClassUtil.getAllFields(paramClass);
//        for(Field field : bodyField){
//            ArgsParamDescription args = createArgsParamForField(field);
//            if(args == null){
//                continue;
//            }
//            argsParam.getChild().add(args);
//        }
//        return argsParam;
//    }


    /**
     * 根据成员变量构建 ArgsParamDescription 参数描述
     * @param field
     * @return
     */
    private ArgsParamDescription createArgsParamForField(int deep, Field field){

        GArgsIgnore ignore = field.getDeclaredAnnotation(GArgsIgnore.class);
        if(ignore != null){
            return null;
        }

        // 防止无线嵌套
        if(deep > 10){
            return null;
        }
        Type fieldClazz = field.getGenericType();
        if(fieldClazz instanceof ParameterizedType){
            ParameterizedType paramClass = (ParameterizedType) fieldClazz;
            Type rowType = paramClass.getRawType();
            if(rowType instanceof Class){
                Class rowClass = (Class)rowType;
                // 如果是MAP类型
                if(Map.class.isAssignableFrom(rowClass)){

                    ApiModelProperty property = field.getDeclaredAnnotation(ApiModelProperty.class);
                    ArgsParamDescription argsParam = new ArgsParamDescription();
                    if(property != null){
                        argsParam.setRequired(property.required());
                        argsParam.setSummary(property.notes());
                        argsParam.setDemo(property.example());
                        argsParam.setNote(property.value());
                        argsParam.setKeyName(property.name());
                    }
                    if(StringUtils.isEmpty(argsParam.getKeyName())){
                        argsParam.setKeyName(field.getName());
                    }
                    argsParam.setType("Object");
                    argsParam.setKeyName(field.getName());
                    return argsParam;
                }

                // 如果是List 或 set 的数组
                if(Collection.class.isAssignableFrom(rowClass) || List.class.isAssignableFrom(rowClass) || Set.class.isAssignableFrom(rowClass)){

                    ApiModelProperty property = field.getDeclaredAnnotation(ApiModelProperty.class);
                    ArgsParamDescription argsParam = new ArgsParamDescription();
                    if(property != null){
                        argsParam.setRequired(property.required());
                        argsParam.setSummary(property.notes());
                        argsParam.setDemo(property.example());
                        argsParam.setNote(property.value());
                        argsParam.setKeyName(property.name());
                    }
                    if(StringUtils.isEmpty(argsParam.getKeyName())){
                        argsParam.setKeyName(field.getName());
                    }

                    argsParam.setType("Array");
                    Type type = paramClass.getActualTypeArguments()[0];
                    if("E".equals(type.getTypeName()) || "?".equals(type.getTypeName()) || "Object".equals(type.getTypeName())){
                        return argsParam;
                    }
                    if(!(type instanceof Class)){
                        return argsParam;
                    }


                    Class actualInnerType = (Class) type;
                    if(ClassUtil.isBasicClass(actualInnerType)){
                        argsParam.setBasicClassArray(true);
                        if(String.class.isAssignableFrom(actualInnerType) || Character.class.isAssignableFrom(actualInnerType)){
                            argsParam.setInnerClass("String");
                        } else {
                            argsParam.setInnerClass("Number");
                        }
//                            ArgsParamDescription inner = new ArgsParamDescription();
//                            inner.setNote(gArgsParam.note());
//                            inner.setKeyName(gArgsParam.value());
//                            inner.setRequired(gArgsParam.required());
//                            inner.setSummary(gArgsParam.summary());
//                            inner.setDemo(gArgsParam.demo());
//                            inner.setType(actualInnerType.getSimpleName());
//                            argsParam.getChild().add(inner);
                        return argsParam;
                    }


                    List<Field> bodyField = ClassUtil.getAllFields((Class) type);
                    argsParam.setChild(new ArrayList<>());
                    for(Field childField : bodyField){
                        ArgsParamDescription args = createArgsParamForField(deep + 1, childField);
                        if(args == null){
                            continue;
                        }
                        argsParam.getChild().add(args);
                    }
                    return argsParam;
                }
            }
        }


        if(fieldClazz instanceof Class){

            // TODO 判断是否为内部类

            // 如果是基本类型
            if(ClassUtil.isBasicClass((Class)fieldClazz)){
                ApiModelProperty property = field.getDeclaredAnnotation(ApiModelProperty.class);
                ArgsParamDescription argsParam = new ArgsParamDescription();
                if(property != null){
                    argsParam.setRequired(property.required());
                    argsParam.setSummary(property.notes());
                    argsParam.setDemo(property.example());
                    argsParam.setNote(property.value());
                    argsParam.setType(((Class<?>) fieldClazz).getSimpleName());
                }
                argsParam.setKeyName(field.getName());
                return argsParam;
            }

            List<Field> bodyField = ClassUtil.getAllFields((Class) fieldClazz);
            ArgsParamDescription argsParam = new ArgsParamDescription();
            ApiModelProperty property = field.getDeclaredAnnotation(ApiModelProperty.class);
            if(property != null){
                argsParam.setRequired(property.required());
                argsParam.setSummary(property.notes());
                argsParam.setDemo(property.example());
                argsParam.setNote(property.value());
                argsParam.setType("Object");
            }
            argsParam.setKeyName(field.getName());
            argsParam.setChild(new ArrayList<>());
            for(Field childField : bodyField){
                ArgsParamDescription args = createArgsParamForField(deep + 1, childField);
                if(args == null){
                    continue;
                }
                argsParam.getChild().add(args);
            }
            return argsParam;
        }
        return null;
    }




    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        if (!this.initialized) {
            this.afterPropertiesSet();
        }
        Method method = methodInvocation.getMethod();

        if (AopUtils.isToStringMethod(method)) {
            return "gateway proxy for service interface [" + this.serviceInterface + "]";
        }

        MethodExecutor executor = methodMap.get(method);
        return executor.execute(methodInvocation.getArguments());
    }

    @Override
    public Object getObject() throws Exception {
        if (this.serviceProxy == null) {
            this.onInit();
            Assert.notNull(this.serviceProxy, "failed to initialize proxy");
        }
        return this.serviceProxy;
    }

    @Override
    public Class<?> getObjectType() {
        return (this.serviceInterface != null ? this.serviceInterface : null);
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public void setBeanClassLoader(ClassLoader beanClassLoader) {
        this.beanClassLoader = beanClassLoader;
    }

    private Class<?> determineServiceInterface() {
        if (this.serviceInterface == null) {
            this.serviceInterface = RequestReplyExchanger.class;
        }
        return this.serviceInterface;
    }


    /**
     * 方法执行器
     * 在初始化时将方法信息提取，提高执行速度
     */
    class MethodExecutor {

        private Method method;

        /**
         * 方法上的标注
         */
        private SenderAnnotationsModel senderAnnotation;

        private ParameterDescriberModel parameterDescriberModel;

        private Class returnType;

        MethodExecutor(Type interfaceType, GMessage gMessage, Method method) {
            this.method = method;

            GSender sender = method.getAnnotation(GSender.class);
            if(sender == null){
                throw new GMessageException("@GMessage批注的接口，每一个方法都需要批注@GSender");
            }
            this.senderAnnotation = new SenderAnnotationsModel(interfaceType, gMessage, method, sender);
            this.parameterDescriberModel = new ParameterDescriberModel(sender, method);
            this.returnType = method.getReturnType();

            System.currentTimeMillis();
        }

        /**
         *
         */
        public Object execute(Object[] arguments) throws Exception {

            String firstCmd = senderAnnotation.getFirstCmd();
            String subCmd = senderAnnotation.getSubCmd();
            int qos = senderAnnotation.getQos();
            boolean share = senderAnnotation.isShare();

            Map<String, Object> args = new HashMap<>();
            for(int i = 0; i < arguments.length; i++){
                Object arg = arguments[i];
                if(arg == null){
                    continue;
                }

                parameterDescriberModel.pushArgs(args, arguments[i], i);
            }

            GMessageModel messageModel = GMessageModel.create(firstCmd, subCmd)
                    .args(args);
            if(!parameterDescriberModel.isZoomAll()){
                String zoom = (String) arguments[parameterDescriberModel.getZoomIndex()];
                messageModel.zoom(zoom);
            }

            IMqttToken token = messageSender.send(qos, share, messageModel);
            if(returnType.equals(token.getClass())){
                return token;
            }


            if("void".equals(returnType.getName())){
                return null;
            }

            CountDownLatch countDownLatch = new CountDownLatch(1);

            AtomicBoolean success = new AtomicBoolean(false);
            token.setActionCallback(new MqttActionListener() {
                @Override
                public void onSuccess(IMqttToken asyncActionToken) {
                    success.set(true);
                    countDownLatch.countDown();
                }

                @Override
                public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
                    success.set(false);
                    countDownLatch.countDown();
                }
            });
            // 等待一秒钟
            countDownLatch.await(1, TimeUnit.SECONDS);

            if("int".equals(returnType.getName()) || returnType.equals(Integer.class)){
                token.waitForCompletion();
                return success.get() ? 1 : 0;
            }
            if("boolean".equals(returnType.getName()) || returnType.equals(Boolean.class)){
                token.waitForCompletion();
                return success.get();
            }
            return null;
        }
    }
}
